/**
 * This file is part of the eConference project and it is distributed under the 
 * terms of the MIT Open Source license.
 * 
 * The MIT License
 * Copyright (c) 2005 Collaborative Development Group - Dipartimento di Informatica, 
 *                    University of Bari, http://cdg.di.uniba.it
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 * software and associated documentation files (the "Software"), to deal in the Software 
 * without restriction, including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to the following 
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies 
 * or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package it.uniba.di.cdg.xcore.econference.jabber;

import it.uniba.di.cdg.jabber.JabberBackend;
import it.uniba.di.cdg.smackproviders.AgendaItemListPacket;
import it.uniba.di.cdg.smackproviders.AgendaOperationPacket;
import it.uniba.di.cdg.smackproviders.ConferenceStatusPacket;
import it.uniba.di.cdg.smackproviders.CurrentAgendaItemPacket;
import it.uniba.di.cdg.smackproviders.MUCPersonalStatusChangedPacket;
import it.uniba.di.cdg.smackproviders.QuestionUpdatePacket;
import it.uniba.di.cdg.smackproviders.RaiseHandIQPacket;
import it.uniba.di.cdg.smackproviders.SmackCommons;
import it.uniba.di.cdg.smackproviders.SpecialPrivilegeNotificationPacket;
import it.uniba.di.cdg.smackproviders.WhiteBoardNotificationPacket;
import it.uniba.di.cdg.xcore.econference.EConferenceContext;
import it.uniba.di.cdg.xcore.econference.IEConferenceService;
import it.uniba.di.cdg.xcore.econference.model.ConferenceModel;
import it.uniba.di.cdg.xcore.econference.model.IConferenceModel;
import it.uniba.di.cdg.xcore.econference.model.IDiscussionItem;
import it.uniba.di.cdg.xcore.econference.model.IConferenceModel.ConferenceStatus;
import it.uniba.di.cdg.xcore.econference.model.hr.HandRaisingModel;
import it.uniba.di.cdg.xcore.econference.model.hr.IHandRaisingModel;
import it.uniba.di.cdg.xcore.econference.model.hr.IQuestion;
import it.uniba.di.cdg.xcore.econference.model.hr.Question;
import it.uniba.di.cdg.xcore.econference.model.hr.IQuestion.QuestionStatus;
import it.uniba.di.cdg.xcore.multichat.jabber.JabberMultiChatService;
import it.uniba.di.cdg.xcore.multichat.model.IParticipant;
import it.uniba.di.cdg.xcore.multichat.model.SpecialPrivilegesAction;
import it.uniba.di.cdg.xcore.network.model.tv.ITalkModel;

import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;

/**
 * Implementation of the Jabber E-Conference service.  
 */
public class JabberEConferenceService extends JabberMultiChatService implements IEConferenceService {
    /**
     * Moderators receive hand raises: when creating an answer they need to assign a unique ID to
     * each.
     */
    private static int s_questionId;

    /**
     * Computes and returns the next question id.
     * 
     * @return a new unique question id
     */
    private synchronized int nextQuestionId() {
        return ++s_questionId;
    }

    /**
     * The collection of pending, approved and rejected questions are grouped in an ad-hoc
     * model. Clients might register as listeners of this model.
     */
    private IHandRaisingModel hrModel;

    /**
     * @param conferenceContext 
     * @param context
     * @param backend
     */
    public JabberEConferenceService( EConferenceContext conferenceContext, JabberBackend backend ) {
        super( conferenceContext, backend );
        this.hrModel = new HandRaisingModel();
    }

    public JabberEConferenceService() {
		super();
		 this.hrModel = new HandRaisingModel();
	}

	/* (non-Javadoc)
     * @see it.uniba.di.cdg.jabber.internal.JabberMultiChatService#createModel()
     */
    @Override
    protected IConferenceModel createModel() {
        IConferenceModel model = new ConferenceModel();
        // Use the item list provided by the context if it is present: typically who is invited 
        // has a context with no item list.
        if (getContext().getItemList() != null)
            model.setItemList( getContext().getItemList() );

        return model;
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.jabber.internal.JabberMultiChatService#createAndRegisterSmackListeners()
     */
    @Override
    protected void createAndRegisterSmackListeners() {
        super.createAndRegisterSmackListeners();

        registerConferenceStatusListener();

        registerItemListChangesListener();

        registerAgendaItemListListener();

        registerAgendaOperationListener();

        registerWhiteboardChangesListener();

        registerSpecialPrivilegeChangesListener();
        
        registerMUCPersonalStatusChangedListener();

        registerQuestionStatusListener();

        registerQuestionUpdateListener();
    }



	protected void registerQuestionUpdateListener() {
		// Receives updates about the questions' status
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final QuestionUpdatePacket ext = (QuestionUpdatePacket) packet.getExtension(
                        QuestionUpdatePacket.ELEMENT_NAME, SmackCommons.CDG_NAMESPACE );

                final IParticipant p = getLocalUserOrParticipant( ext.getWho() );

                if (p == null)
                    return;

                QuestionStatus status = QuestionStatus.valueOf( ext.getStatus() );

                IQuestion existing = getHandRaisingModel().getQuestion( ext.getId() );
                // Unknown question? Just add it to the model. A question with the same id as an
                // old one? Replace the previous one too!
                if (existing == null
                        || (existing != null && QuestionStatus.PENDING.equals( status ))) {
                    // This user is the moderator someone has asked to raise hand
                    final IQuestion q = new Question( getHandRaisingModel(), ext.getId(), ext
                            .getQuestionText(), ext.getWho(), status );
                    getHandRaisingModel().addQuestion( q );
                }
                // Otherwise rejected or approved questions are simply removed from model:
                // the controller is responsible for performing additional functions, like
                // freezing or unfreezing.
                else if (QuestionStatus.REJECTED.equals( status )) {
                    notifyLocalSystemMessage( String.format(
                            "Moderator has rejected the following question from %s:\n\"%s\"", p
                                    .getNickName(), existing.getText() ) );
                    getHandRaisingModel().removeQuestion( existing );
                } else if (QuestionStatus.APPROVED.equals( status )) {
                    notifyLocalSystemMessage( String.format(
                            "Moderator has approved the following question from %s:\n\"%s\"", p
                                    .getNickName(), existing.getText() ) );
                    getHandRaisingModel().removeQuestion( existing );
                }
            }
        };
        addPacketListener( packetListener, QuestionUpdatePacket.FILTER );
	}

	protected void registerQuestionStatusListener() {
		// Captures hand raise requests and handles them if they are directed to this local user
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final RaiseHandIQPacket rh = (RaiseHandIQPacket) packet;

                final IParticipant p = getLocalUserOrParticipant( rh.getWho() );
                // We rely on UI to check against our roles: so if the client asks us a question
                // we just accept it: if stronger checks are needed then this is a good place
                // to enforce them.
                //                if (!Role.MODERATOR.equals( getModel().getLocalUser().getRole() ))
                //                    return;

                if (p == null)
                    return;

                // This user is the moderator someone has asked to raise hand 
                final IQuestion question = new Question( null, nextQuestionId(), rh
                        .getQuestionText(), rh.getFrom(), QuestionStatus.PENDING );

                notifyQuestionUpdate( question );
            }
        };
        addPacketListener( packetListener, RaiseHandIQPacket.FILTER );
	}

	protected void registerSpecialPrivilegeChangesListener() {
		// Special role changes (needed, i.e., when someone is given "scribe" special role.
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final SpecialPrivilegeNotificationPacket ext = (SpecialPrivilegeNotificationPacket) packet
                        .getExtension( SpecialPrivilegeNotificationPacket.ELEMENT_NAME,
                                SmackCommons.CDG_NAMESPACE );

                String who = ext.getWho();
                String privilegeAction = ext.getAction();
                IParticipant p = getLocalUserOrParticipant( who );

                if (p == null) {
                    System.out.println( "Received privilege change for unknown participant id: " + who );
                    return;
                }
                // The participant will fire an event
                if(privilegeAction.equals(SpecialPrivilegesAction.GRANT))
                	p.addSpecialPriviliges( ext.getSpecialPrivilege() );
                else if(privilegeAction.equals(SpecialPrivilegesAction.REVOKE))
                	p.removeSpecialPrivileges( ext.getSpecialPrivilege() );
                else{
                	System.out.println( "Received unknown privilege action for participant id: " + who );
                    return;
                }
                	
                	
            }
        };
        addPacketListener( packetListener, SpecialPrivilegeNotificationPacket.FILTER );
	}
	
	private void registerMUCPersonalStatusChangedListener() {
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final MUCPersonalStatusChangedPacket ext = (MUCPersonalStatusChangedPacket) packet
                        .getExtension( MUCPersonalStatusChangedPacket.ELEMENT_NAME,
                                SmackCommons.CDG_NAMESPACE );

                String who = ext.getWho();
                String personalStatus = ext.getPersonalStatus();
                IParticipant p = getLocalUserOrParticipant( who );

                if (p == null) {
                    System.out.println( "Received personal status changed for unknown participant id: " + who );
                    return;
                }
                // The participant will fire an event
                p.setPersonalStatus(personalStatus);
                	
            }
        };
		addPacketListener( packetListener, MUCPersonalStatusChangedPacket.FILTER );
	}

	protected void registerWhiteboardChangesListener() {
		// Whiteboard changes
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final WhiteBoardNotificationPacket ext = (WhiteBoardNotificationPacket) packet
                        .getExtension( WhiteBoardNotificationPacket.ELEMENT_NAME,
                                SmackCommons.CDG_NAMESPACE );

                String who = ext.getWho();
                // Try to use a nickname (friendlier to see), if available
                IParticipant p = getLocalUserOrParticipant( who );
                if (p != null)
                    who = p.getNickName();

                getModel().setWhiteBoardText( ext.getText() );
            }
        };
        addPacketListener( packetListener, WhiteBoardNotificationPacket.FILTER );
	}

	protected void registerAgendaOperationListener() {
		// Listen to incoming packets regarding the item list content
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final AgendaOperationPacket ext = (AgendaOperationPacket) packet.getExtension(
                        AgendaOperationPacket.ELEMENT_NAME, SmackCommons.CDG_NAMESPACE );
                AgendaOperation op = AgendaOperation.valueOf( ext.getOperation() );
                // Every operation has its context: ADD just want a string containing 
                // the text associated to the new item, REMOVE probably will want to 
                // parse the string to an integer index.
                String context = ext.getContext();
                if (AgendaOperation.ADD.equals( op )) {
                    getModel().getItemList().addItem( context );
                } else {
                    System.err.println( "Unsupported operation " + ext.getOperation() );
                }
            }
        };
        addPacketListener( packetListener, AgendaOperationPacket.FILTER );
	}

	protected void registerAgendaItemListListener() {
		// Listen to incoming packets regarding the item list content
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final AgendaItemListPacket ext = (AgendaItemListPacket) packet.getExtension(
                        AgendaItemListPacket.ELEMENT_NAME, SmackCommons.CDG_NAMESPACE );
                getModel().getItemList().decode( ext.getEncodedItems() );
            }
        };
        addPacketListener( packetListener, AgendaItemListPacket.FILTER );
	}

	protected void registerItemListChangesListener() {
		// item list changes
		 PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final CurrentAgendaItemPacket ext = (CurrentAgendaItemPacket) packet.getExtension(
                        CurrentAgendaItemPacket.ELEMENT_NAME, SmackCommons.CDG_NAMESPACE );

                if (!ITalkModel.FREE_TALK_THREAD_ID.equals( ext.getItemId() )) {
                    int itemId = Integer.parseInt( ext.getItemId() );
                    // Note that we update *** independently ***  the agenda item list 
                    // and talk model (which handles the discussion threads)
                    try {
                        getModel().getItemList().setCurrentItemIndex( itemId );
                        String newSubject = ((IDiscussionItem)getModel().getItemList().getItem( itemId )).getText();
                        getModel().setSubject( newSubject, "[System]" );
                    } catch (IllegalArgumentException e) {
                        // the item list will protest if we set an out-of-range index:
                        // here it is just safe ignoring its cries ...
                    	e.printStackTrace();
                    }
                }
                getTalkModel().setCurrentThread( ext.getItemId() );
            }
        };
        addPacketListener( packetListener, CurrentAgendaItemPacket.FILTER );
	}

	protected void registerConferenceStatusListener() {
		// Conference status
		PacketListener packetListener = new PacketListener() {
            public void processPacket( Packet packet ) {
                final ConferenceStatusPacket statusExt = (ConferenceStatusPacket) packet
                        .getExtension( ConferenceStatusPacket.ELEMENT_NAME,
                                SmackCommons.CDG_NAMESPACE );

                ConferenceStatus status = ConferenceStatus.valueOf( statusExt.getStatus() );
                if (status != null)
                    getModel().setStatus( status );
                else
                    System.out.println( "Invalid status received: " + statusExt.getStatus() );
            }
        };
        addPacketListener( packetListener, ConferenceStatusPacket.FILTER );
	}

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.jabber.internal.JabberMultiChatService#leave()
     */
    @Override
    public void leave() {
        super.leave();
        getHandRaisingModel().dispose();
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.multichat.service.IMultiChatService#getModel()
     */
    public IConferenceModel getModel() {
        return (IConferenceModel) super.getModel();
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#getHandRaisingModel()
     */
    public IHandRaisingModel getHandRaisingModel() {
        return hrModel;
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#getConferenceContext()
     */
    public EConferenceContext getContext() {
        return (EConferenceContext) super.getContext();
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyStatusChange(it.uniba.di.cdg.xcore.econference.IEConferenceService.ConferenceStatus)
     */
    public void notifyStatusChange( ConferenceStatus status ) {
        System.out.println( String.format( "notifyStatusChange( %s )", status ) );
        sendCustomExtension( new ConferenceStatusPacket( status.toString() ) );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyWhiteBoardChanged(java.lang.String)
     */
    public void notifyWhiteBoardChanged( String text ) {
        System.out.println( String.format( "notifyWhiteBoardChange( %s )", text ) );
        sendCustomExtension( new WhiteBoardNotificationPacket( getModel().getLocalUser().getId(),
        		StringUtils.escapeForXML( text ) ) );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyChangedSpecialPrivilege(it.uniba.di.cdg.xcore.multichat.model.IParticipant, java.lang.String)
     */
    public void notifyChangedSpecialPrivilege( IParticipant participant, String specialRole, String action ) {
        System.out.println( String.format( "notifyChangedSpecialPrivilege( %s, %s %s )", participant.getId(),
        		action, specialRole ) );
        sendCustomExtension( new SpecialPrivilegeNotificationPacket( StringUtils
        		.escapeForXML( participant.getId() ), specialRole, action ) );
    }
    
    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyChangedSpecialPrivilege(it.uniba.di.cdg.xcore.multichat.model.IParticipant, java.lang.String)
     */
    public void notifyChangedMUCPersonalPrivilege( IParticipant participant, String personalStatus ) {
        System.out.println( String.format( "notifyChangedMUCPersonalStatus( %s, %s )", participant.getId(),
        		personalStatus ) );
        sendCustomExtension( new MUCPersonalStatusChangedPacket( StringUtils
        		.escapeForXML( participant.getId() ), personalStatus ) );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyRaiseHand(java.lang.String, java.lang.String)
     */
    public void notifyRaiseHand( String question, String moderatorId ) {
        System.out.println( String.format( "notifyRaiseHand( %s, %s )", question, moderatorId ) );
        String localId = getModel().getLocalUser().getId();

        sendCustomPacket(
                new RaiseHandIQPacket( localId, StringUtils.escapeForXML( question ) ),
                moderatorId );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyQuestionUpdate(it.uniba.di.cdg.xcore.econference.model.hr.IQuestion)
     */
    public void notifyQuestionUpdate( IQuestion q ) {
        System.out.println( String.format( "notifyQuestionUpdate( %s )", q.getText() ) );
        sendCustomExtension( new QuestionUpdatePacket( q.getId(), StringUtils.escapeForXML( q
                .getWho() ), StringUtils.escapeForXML( q.getText() ), q.getStatus().toString() ) );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyCurrentAgendaItemChanged(java.lang.String)
     */
    public void notifyCurrentAgendaItemChanged( String itemId ) {
        System.out.println( String.format( "notifyCurrentAgendaItemChanged( %s )", itemId ) );
        sendCustomExtension( new CurrentAgendaItemPacket( StringUtils.escapeForXML( itemId ) ) );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyItemListToRemote(java.lang.String)
     */
    public void notifyItemListToRemote() {
        System.out.println( "notifyItemListToRemote" );
        sendCustomExtension( new AgendaItemListPacket( StringUtils.escapeForXML( getModel()
                .getItemList().encode() ) ) );
    }

    /* (non-Javadoc)
     * @see it.uniba.di.cdg.xcore.econference.IEConferenceService#notifyAgendaOperation(it.uniba.di.cdg.xcore.econference.IEConferenceService.AgendaOperation, java.lang.Object)
     */
    public void notifyAgendaOperation( AgendaOperation op, Object context ) {
        System.out.println( String.format( "notifyAgendaOperation( %s, %s )", op, context ) );
        sendCustomExtension( new AgendaOperationPacket( op.toString(), context.toString() ) );
    }
}
